import java.net.*;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.io.*;

/*Worker thread of server class, handles a client by either doing the request
or forwarding request to another node*/

public class ReceiverThread extends Thread {

	private Socket socket = null;
	private FingerTable ft;
	private ConcurrentHashMap<Integer,String> keys;
	private HashMap<Integer, MachineSocket> mSockets;

	public ReceiverThread(Socket socket, FingerTable ft, ConcurrentHashMap<Integer,String> keys) {
		this.socket = socket; //socket to client
		this.ft = ft;
		this.keys = keys;
		this.mSockets = new HashMap<Integer, MachineSocket>();

		for(int i = 0; i < ft.tableList.size(); i++) {
			this.mSockets.put(new Integer(ft.tableList.get(i)), new MachineSocket(ft.tableList.get(i), ft)); //store sockets to nodes in finger table
		}
	}

	public void run() {

		try {

			PrintWriter out = new PrintWriter(socket.getOutputStream(),
					true);
			BufferedReader in = new BufferedReader(new InputStreamReader(
					socket.getInputStream()));

			while(!socket.isClosed()) {

				String data = in.readLine(); //check for another request from client

				if(data != null) {
					ReceivedData d = new ReceivedData(data, socket.getInetAddress(), socket.getPort());

					process(d); //process request
				}
			}

			out.close();
			in.close();
			socket.close();
			
			for(MachineSocket s: mSockets.values()) {
				s.close();
			}
		}
		catch (IOException e) {
			e.printStackTrace();
		}		
	}

	public void process(ReceivedData d) {
		//first determine if we have the correct server, we may need to forward.
		System.out.println((d.key % ft.N) + " " + ft.index);
		if(d.key % ft.N == ft.index) { //just send back to client

			//System.out.println("here");

			if(d.todo.equals("GET")) {

				String message = "null";

				try { //could be null if not in map
					message = (keys.get(new Integer(d.key))).toString();
				}
				catch (NullPointerException e) {
				}

				try {
					//System.out.println(d.name);	
					Socket s = new Socket(d.originalName, Global.clientPortr); //send back to client

					DataOutputStream outToServer = new DataOutputStream(s.getOutputStream());
					BufferedReader inFromServer = new BufferedReader(new InputStreamReader(s.getInputStream()));
					outToServer.writeBytes(message + '\n');

					s.close();

				} catch (UnknownHostException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				} catch (Exception e) {e.printStackTrace();}
			}
			else if(d.todo.equals("INSERT")) {
				String message = "INSERTED " + d.key;
				keys.put(new Integer(d.key), new String(d.value));

				try {
					System.out.println(d.originalName);
					Socket s = new Socket(d.originalName, Global.clientPortr);

					DataOutputStream outToServer = new DataOutputStream(s.getOutputStream());
					BufferedReader inFromServer = new BufferedReader(new InputStreamReader(s.getInputStream()));
					outToServer.writeBytes(message + '\n');

					System.out.println(message);

					s.close();

				} catch (UnknownHostException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}

				System.out.println("SIZE: " + keys.size());
			}
			else if(d.todo.equals("DELETE")) {
				String message = "DELETED " + d.key;
				keys.remove(new Integer(d.key));

				try {

					Socket s = new Socket(d.originalName, Global.clientPortr);

					DataOutputStream outToServer = new DataOutputStream(s.getOutputStream());
					BufferedReader inFromServer = new BufferedReader(new InputStreamReader(s.getInputStream()));
					outToServer.writeBytes(message + '\n');

					s.close();

				} catch (UnknownHostException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		else { //forward to next node

			System.out.println("forwarding");
			int x = Global.numForwards += 1;

			int m = ft.get(d.key); //obtain index of node where we are going to forward

			//System.out.println(d.ip.getHostAddress());
			//System.out.println(d.originalName);
			String message = "FORWARD " + d.todo + " " + d.key +" " + d.originalName + " " + Global.clientPortr;

			if(d.todo.equals("INSERT")) {
				message = message + " " + d.value;
			}

			
			MachineSocket s = mSockets.get(m);
			s.write(message);

			System.out.println("Forwards: " + x);
		}
	}

	//class used to create and store sockets to other nodes
	private class MachineSocket {

		private FingerTable ft;
		public Socket s;
		public DataOutputStream outToServer;
		public BufferedReader inFromServer; 

		public MachineSocket(int i, FingerTable ft) {
			this.ft = ft;

			MachineData m = ft.machines.get(i);
			Socket s;
			try {
				s = new Socket(m.name, m.port);
				outToServer = new DataOutputStream(s.getOutputStream());
				inFromServer = new BufferedReader(new InputStreamReader(s.getInputStream()));
				//outToServer.writeBytes(message + '\n');
			} catch (UnknownHostException e) {
				e.printStackTrace();
			} catch (IOException e) {
				//e.printStackTrace();
			}
		}

		public void write(String message) {
			try {
				outToServer.writeBytes(message + '\n');
			} catch (UnknownHostException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		public void close() {
			try {
				s.close();
			} catch (UnknownHostException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
